using System;
using System.Collections.Generic;
using System.ComponentModel;
using CSW.Framework.Common.Binding.Interfaces;

namespace CSW.Framework.Common.Binding
{
    /// <summary>
    /// Represents a list of selected items and a list of available items.
    /// </summary>
    /// <typeparam name="T">A type that implements INotifyPropertyChanged.</typeparam>
    public class DataSelectionList<T> : IDataSelectionList where T : INotifyPropertyChanged
    {
        private readonly BindingList<T> m_InitialList;
        private readonly BindingList<T> m_AvailableList = new BindingList<T>();
        private readonly BindingList<T> m_SelectedList = new BindingList<T>();

        private readonly BindingList<T> m_SavedAvailableList = new BindingList<T>();
        private readonly BindingList<T> m_SavedSelectedList = new BindingList<T>();

        /// <summary>
        /// Initializes a new instance of the <see cref="DataSelectionList&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="initialList">The initial list of items.</param>
        public DataSelectionList(BindingList<T> initialList)
        {
            Guard.ArgumentNotNullOrEmpty<T>(initialList, "initialList");

            m_InitialList = initialList;

            foreach (T item in m_InitialList)
            {
                m_AvailableList.Add(item);
                m_SavedAvailableList.Add(item);
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataSelectionList&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="initialList">The initial list of items.</param>
        /// <param name="selectedList">The list of selected items.</param>
        public DataSelectionList(BindingList<T> initialList, BindingList<T> selectedList)
        {
            Guard.ArgumentNotNullOrEmpty<T>(initialList, "initialList");
            Guard.ArgumentNotNull(selectedList, "selectedList");

            m_InitialList = initialList;
            m_SelectedList = selectedList;

            foreach (T item in m_InitialList)
            {
                m_AvailableList.Add(item);
                m_SavedAvailableList.Add(item);
            }

            foreach (T item in m_SelectedList)
            {
                m_AvailableList.Remove(item);
                m_SavedAvailableList.Remove(item);

                m_SavedSelectedList.Add(item);
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="DataSelectionList&lt;T&gt;"/> class.
        /// </summary>
        public DataSelectionList()
        {
            m_InitialList = new BindingList<T>();
        }

        /// <summary>
        /// Gets the available items.
        /// </summary>
        /// <value>The available items.</value>
        public BindingList<T> AvailableItems
        {
            get { return m_AvailableList; }
        }

        /// <summary>
        /// Gets the selected items.
        /// </summary>
        /// <value>The selected items.</value>
        public BindingList<T> SelectedItems
        {
            get { return m_SelectedList; }
        }

        /// <summary>
        /// Gets the available items.
        /// </summary>
        /// <value>The available items.</value>
        Object IDataSelectionList.AvailableItems
        {
            get { return m_AvailableList; }
        }

        /// <summary>
        /// Gets the selected items.
        /// </summary>
        /// <value>The selected items.</value>
        Object IDataSelectionList.SelectedItems
        {
            get { return m_SelectedList; }
        }

        /// <summary>
        /// Selects an item.
        /// </summary>
        /// <param name="item">The item to select.</param>
        public void SelectItem(T item)
        {
            Guard.ArgumentNotNull(item, "item");

            if (!m_InitialList.Contains(item))
                throw new Exception(String.Format("Item '{0}' not in initial list", item));
            if (m_SelectedList.Contains(item))
                throw new Exception(String.Format("Item '{0}' is already selected", item));
            if (!m_AvailableList.Contains(item))
                throw new Exception(String.Format("Item '{0}' not in available list", item));

            m_AvailableList.Remove(item);
            m_SelectedList.Add(item);
        }

        /// <summary>
        /// Selects all items.
        /// </summary>
        public void SelectAll()
        {
            m_AvailableList.Clear();
            m_SelectedList.Clear();

            foreach (T item in m_InitialList)
                m_SelectedList.Add(item);
        }

        /// <summary>
        /// Deselects an item.
        /// </summary>
        /// <param name="item">The item to deselect.</param>
        public void DeselectItem(T item)
        {
            Guard.ArgumentNotNull(item, "item");

            if (!m_InitialList.Contains(item))
                throw new Exception(String.Format("Item '{0}' not in initial list", item));
            if (m_AvailableList.Contains(item))
                throw new Exception(String.Format("Item '{0}' is already deselected", item));
            if (!m_SelectedList.Contains(item))
                throw new Exception(String.Format("Item '{0}' is not in selected list", item));

            m_SelectedList.Remove(item);
            m_AvailableList.Add(item);
        }

        /// <summary>
        /// Deselects all items.
        /// </summary>
        public void DeselectAll()
        {
            m_AvailableList.Clear();
            m_SelectedList.Clear();

            foreach (T item in m_InitialList)
                m_AvailableList.Add(item);
        }

        /// <summary>
        /// Reverts to saved.
        /// </summary>
        public void RevertToSaved()
        {
            m_AvailableList.Clear();
            m_SelectedList.Clear();

            foreach (T item in m_SavedAvailableList)
                m_AvailableList.Add(item);

            foreach (T item in m_SavedSelectedList)
                m_SelectedList.Add(item);
        }

        /// <summary>
        /// Accepts the changes.
        /// </summary>
        public void AcceptChanges()
        {
            m_SavedAvailableList.Clear();
            m_SavedSelectedList.Clear();

            foreach (T item in m_AvailableList)
                m_SavedAvailableList.Add(item);

            foreach (T item in m_SelectedList)
                m_SavedSelectedList.Add(item);
        }

        /// <summary>
        /// Removes a batch of items from the selected list.
        /// </summary>
        /// <param name="list">The list.</param>
        public void RemoveBatch(IEnumerable<T> list)
        {
            // TODO: should this remove from AvailableList?

            Guard.ArgumentNotNull(list, "list");

            foreach (T item in list)
            {
                m_SelectedList.Remove(item);
            }
        }

        #region IDataSelectionList Members

        void IDataSelectionList.SelectItem(object item)
        {
            SelectItem((T)item);
        }

        void IDataSelectionList.DeselectItem(object item)
        {
            DeselectItem((T)item);
        }

        #endregion
    }
}